package cn.aitrox.ry.service.anno.service.impl;

import cn.aitrox.ry.bean.RespResult;
import cn.aitrox.ry.common.context.SpringContext;
import cn.aitrox.ry.common.service.BaseService;
import cn.aitrox.ry.constant.CommonConstant;
import cn.aitrox.ry.enumtype.RespCodeEnum;
import cn.aitrox.ry.service.alg.dto.ultrasound.video.in.InfoDto;
import cn.aitrox.ry.service.alg.dto.ultrasound.video.in.NoduleItemDto;
import cn.aitrox.ry.service.alg.dto.ultrasound.video.in.VideoTraceInDto;
import cn.aitrox.ry.service.alg.dto.ultrasound.video.out.VideoTraceOutDto;
import cn.aitrox.ry.service.alg.dto.ultrasound.video.out.VideoTraceResultFileDto;
import cn.aitrox.ry.service.alg.dto.ultrasound.video.out.VideoTraceResultItemDto;
import cn.aitrox.ry.service.alg.feign.UltrasoundVideoFeign;
import cn.aitrox.ry.service.anno.bo.lesion.result.group.GroupInfoBo;
import cn.aitrox.ry.service.anno.bo.ultrasound.video.VedioTraceCheckResultBo;
import cn.aitrox.ry.service.anno.bo.ultrasound.video.VideoAnnoInfoBo;
import cn.aitrox.ry.service.anno.config.AnnoNacosProperties;
import cn.aitrox.ry.service.anno.dao.AnnoComponentResultDao;
import cn.aitrox.ry.service.anno.dao.AnnoLesionResultDao;
import cn.aitrox.ry.service.anno.dao.AnnoLesionResultGroupDao;
import cn.aitrox.ry.service.anno.dao.AnnoLesionToolDao;
import cn.aitrox.ry.service.anno.dao.AnnoResultDao;
import cn.aitrox.ry.service.anno.entity.AnnoFileHandlerInfoEntity;
import cn.aitrox.ry.service.anno.entity.AnnoLesionResultEntity;
import cn.aitrox.ry.service.anno.entity.AnnoLesionResultGroupEntity;
import cn.aitrox.ry.service.anno.entity.AnnoLesionToolEntity;
import cn.aitrox.ry.service.anno.entity.AnnoResultEntity;
import cn.aitrox.ry.service.anno.enumtype.FileHandlerBizTypeEnum;
import cn.aitrox.ry.service.anno.enumtype.anno.LesionResultGroupTypeEnum;
import cn.aitrox.ry.service.anno.service.AnnoUltrasoundService;
import cn.aitrox.ry.service.anno.service.FileHandlerInfoService;
import cn.aitrox.ry.service.config.dto.tool.result.struct.rectangle.RectangleStruct;
import cn.aitrox.ry.service.config.enumtype.LesionToolBizTypeEnum;
import cn.aitrox.ry.service.config.enumtype.ToolEnum;
import cn.aitrox.ry.util.ArrayUtil;
import cn.aitrox.ry.util.DateUtil;
import cn.aitrox.ry.util.FsUtil;
import cn.aitrox.ry.util.JSONUtil;
import cn.aitrox.ry.util.LogUtil;
import cn.aitrox.ry.util.RectangleUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class AnnoUltrasoundServiceImpl extends BaseService implements AnnoUltrasoundService {

    private static final Logger LOG = LoggerFactory.getLogger(AnnoUltrasoundServiceImpl.class);

    @Autowired
    private AnnoNacosProperties annoNacosProperties;

    @Autowired
    private AnnoResultDao arDao;

    @Autowired
    private AnnoLesionResultGroupDao alrGroupDao;

    @Autowired
    private AnnoLesionResultDao alrDao;

    @Autowired
    private AnnoLesionToolDao altDao;

    @Autowired
    private AnnoLesionResultGroupDao groupDao;

    @Autowired
    @Lazy
    private UltrasoundVideoFeign ultrasoundVideoFeign;

    @Autowired
    private SpringContext springContext;

    @Autowired
    private AnnoComponentResultDao acrDao;

    @Autowired
    private FileHandlerInfoService fileHandlerInfoService;


    @Override
    public RespResult videoTrace(Integer arId) {

        AnnoResultEntity ar = arDao.findById(arId);
        if (null == ar) {
            return RespResult.error(RespCodeEnum.NOT_EXIST);
        }

        // 校验是否设置了追踪病灶以及对应的矩形工具
        RespResult<AnnoLesionToolEntity> respResult = this.checkLesionAndType4VideoTrace(ar.getTaskId());
        if (!respResult._isOk()) {
            return respResult;
        }

        AnnoLesionToolEntity alt = respResult.getData();

        // 查询所有的矩形病灶结果
        AnnoLesionResultEntity alrCond = new AnnoLesionResultEntity();
        alrCond.setArId(arId);
        alrCond.setTool(ToolEnum.RECTANGLE.getValue());
        List<AnnoLesionResultEntity> alrList = alrDao.findByCondition(alrCond, null);

        if (ArrayUtil.isEmpty(alrList)) {
            return RespResult.secByError(RespCodeEnum.NOT_EXIST.getCode(), "没有可追踪的病灶");
        }

        // 排除单帧标注的病灶，无需追踪
        alrList = alrList.stream().filter(x -> x.getGroupId() > 0).collect(Collectors.toList());
        if (ArrayUtil.isEmpty(alrList)) {
            return RespResult.secByError(RespCodeEnum.NOT_EXIST.getCode(), "没有起始、结束、关键帧");
        }
        Map<Integer, List<AnnoLesionResultEntity>> groupAlrsMap = alrList.stream().collect(Collectors.groupingBy(AnnoLesionResultEntity::getGroupId));
        // 校验：除去算法结果是否有且只有三帧：起始帧、结束帧、关键帧，并且位置是否符合要求
        RespResult<List<VedioTraceCheckResultBo>> checkResult = this.checkAlr4VideoTrace(ar.getId(), groupAlrsMap);
        if (!checkResult._isOk()) {
            return RespResult.secByError(checkResult.getCode(), checkResult.getMsg());
        }

        List<VedioTraceCheckResultBo> checkResultList = checkResult.getData();

        // 下载视频文件到算法共享目录用于给算法追踪
        RespResult<AnnoFileHandlerInfoEntity> downloadVideoResult = fileHandlerInfoService.downloadImage4Task(FileHandlerBizTypeEnum.VIDEO_DOWNLOAD, ar.getTaskId(), ar.getSeriesUid());
        if (!downloadVideoResult._isOk()) {
            return downloadVideoResult;
        }

        // 封装算法请求参数
        VideoTraceInDto reqDto = this.packVideoTraceReqDto(checkResultList, downloadVideoResult.getData().getFileTargetPath());

        if (ArrayUtil.isEmpty(reqDto.getNodule())) {
            return RespResult.secByError(RespCodeEnum.ERROR.getCode(), "没有需要追踪的结节");
        }

        try {
            // 请求超声算法
            RespResult<VideoTraceOutDto> videoTraceRespResult;
            try {
                videoTraceRespResult = ultrasoundVideoFeign.videoTrace(reqDto);
            } catch (Exception e) {
                LOG.error(LogUtil.genException(e, "超声视频追踪异常", "arId", arId.toString()), e);
                return RespResult.secByError(RespCodeEnum.ERROR.getCode(), "追踪失败，请重试");
            }
            if(!videoTraceRespResult._isOk()) {
                return RespResult.secByError(videoTraceRespResult.getCode(), videoTraceRespResult.getMsg());
            }
            if (videoTraceRespResult.getData().getCode() != 200) {
                LOG.error(LogUtil.gen("超声视频追踪异常", "arId", arId.toString(), "code", videoTraceRespResult.getData().getCode() + ""));
                return RespResult.secByError(RespCodeEnum.ERROR.getCode(), "追踪失败，请重试");
            }

            // 处理结果
            AnnoUltrasoundService annoUltrasoundService = (AnnoUltrasoundService) springContext.getBean(AnnoUltrasoundService.class);

            annoUltrasoundService.handleVideoTraceResult(ar, reqDto, checkResultList, alt);

            return RespResult.ok();
        } finally {
            FsUtil.deleteDir(reqDto.getResJsonSaveFolder());
        }
    }

    @Transactional
    @Override
    public void handleVideoTraceResult(AnnoResultEntity ar, VideoTraceInDto reqDto, List<VedioTraceCheckResultBo> checkResultList, AnnoLesionToolEntity alt) {

        List<Integer> algAlrIds2Delete = new ArrayList<>();

        List<AnnoLesionResultEntity> alrs2Save = new ArrayList<>();

        List<Integer> groupId2Update = new ArrayList<>();

        ToolEnum toolEnum = ToolEnum.getEnumByValue(alt.getTool());

        reqDto.getNodule().forEach(noduleItemDto -> {
            String resultFilePath = StringUtils.joinWith(CommonConstant.DEFAULT_FILE_SEPARATOR, reqDto.getResJsonSaveFolder(), FsUtil.getFileName(reqDto.getVideo_file()), noduleItemDto.getId() + ".json");
            String jsonStr = FsUtil.readFile(resultFilePath, CommonConstant.CHARACTER_UTF8);
            VideoTraceResultFileDto itemDto = JSONUtil.parseObject(jsonStr, VideoTraceResultFileDto.class);

            // 创建算法结果病灶（排除掉起始、关键、结束的帧）

            VedioTraceCheckResultBo checkResultBo = checkResultList.stream().filter(x -> x.getGroupId().equals(itemDto.getNoduleId())).findFirst().orElse(null);

            if (!ArrayUtil.isEmpty(checkResultBo.getAlgAlrList())) {
                List<Integer> algAlrIds = checkResultBo.getAlgAlrList().stream().map(AnnoLesionResultEntity::getId).distinct().collect(Collectors.toList());
                algAlrIds2Delete.addAll(algAlrIds);
            }

            groupId2Update.add(checkResultBo.getGroupId());

            for (VideoTraceResultItemDto traceResultItem : itemDto.getTracker_result()) {
                if (ArrayUtil.isEmpty(traceResultItem.getBbox())) {
                    continue;
                }

                if (traceResultItem.getFrameNumber().equals(checkResultBo.getStartFrameAnno().getRectangle().getData().getPoint1().getZ()) ||
                        traceResultItem.getFrameNumber().equals(checkResultBo.getMidFrameAnno().getRectangle().getData().getPoint1().getZ()) ||
                        traceResultItem.getFrameNumber().equals(checkResultBo.getEndFrameAnno().getRectangle().getData().getPoint1().getZ())) {
                    continue;
                }
                RectangleStruct rectangleStruct = RectangleStruct.createV1(traceResultItem.getBbox(), traceResultItem.getFrameNumber());
                String resultJson = JSONUtil.toJson(rectangleStruct, RectangleStruct.class);
                AnnoLesionResultEntity alr = new AnnoLesionResultEntity(null, ar.getTaskId(), ar.getId(), alt.getLesion(), alt.getTool(), resultJson, null, toolEnum.getType(), null, null, checkResultBo.getGroupId(), true);
                alrs2Save.add(alr);
            }
        });

        // 删除原来的算法病灶以及小征象结果
        alrDao.deleteByIds(algAlrIds2Delete.toArray());

        acrDao.deleteByAlrIds(algAlrIds2Delete);

        if (!ArrayUtil.isEmpty(alrs2Save)) {
            alrDao.batchSave(alrs2Save);
        }

        // 更新帧段信息 是否跑过算法
        if (!ArrayUtil.isEmpty(groupId2Update)) {
            AnnoLesionResultGroupEntity groupUpdate = new AnnoLesionResultGroupEntity();
            groupUpdate.setAlgProcessed(true);
            groupUpdate.setUpdateTime(new Date());
            groupDao.updateByIdsSelective(groupUpdate, groupId2Update.toArray());
        }
    }

    private RespResult<List<VedioTraceCheckResultBo>> checkAlr4VideoTrace(Integer arId, Map<Integer, List<AnnoLesionResultEntity>> groupAlrsMap) {
        List<AnnoLesionResultGroupEntity> groupList = groupDao.findByCondition(new AnnoLesionResultGroupEntity(null, null, arId, LesionResultGroupTypeEnum.VIDEO_FRAME_SEGMENT.getType(), null, null), null);
        Map<Integer, AnnoLesionResultGroupEntity> groupMap = groupList.stream().collect(Collectors.toMap(AnnoLesionResultGroupEntity::getId, x -> x));

        List<VedioTraceCheckResultBo> checkResultList = new ArrayList<>();

        for (Integer groupId : groupAlrsMap.keySet()) {
            List<AnnoLesionResultEntity> alrsList = groupAlrsMap.get(groupId);
            AnnoLesionResultGroupEntity group = groupMap.get(groupId);

            VedioTraceCheckResultBo checkResult = new VedioTraceCheckResultBo();
            checkResultList.add(checkResult);

            checkResult.setGroupId(groupId);
            checkResult.setArId(group.getArId());

            GroupInfoBo groupInfo = JSONUtil.parseObject(group.getInfo(), GroupInfoBo.class);

            if (groupInfo.getStartFrame().intValue() >= groupInfo.getEndFrame().intValue()) {
                return RespResult.secByError(RespCodeEnum.ERROR.getCode(), "帧段：" + groupInfo.getStartFrame() + " - " + groupInfo.getEndFrame() + "不符合要求");
            }

            List<AnnoLesionResultEntity> algAlrList = alrsList.stream().filter(x -> x.getAlg()).collect(Collectors.toList());
            List<AnnoLesionResultEntity> annoAlrList = alrsList.stream().filter(x -> !x.getAlg()).collect(Collectors.toList());

            if (annoAlrList.size() != 3) {
                return RespResult.secByError(RespCodeEnum.ERROR.getCode(), "帧段：" + groupInfo.getStartFrame() + " - " + groupInfo.getEndFrame() + " 标注框不符合要求");
            }

            List<VideoAnnoInfoBo> videoAnnoInfoList = annoAlrList.stream().map(VideoAnnoInfoBo::new).collect(Collectors.toList());
            VideoAnnoInfoBo startFrameAnno = videoAnnoInfoList.stream().filter(x -> groupInfo.getStartFrame().equals(x.getRectangle().getData().getPoint1().getZ())).findFirst().orElse(null);
            VideoAnnoInfoBo endFrameAnno = videoAnnoInfoList.stream().filter(x -> groupInfo.getEndFrame().equals(x.getRectangle().getData().getPoint1().getZ())).findFirst().orElse(null);
            VideoAnnoInfoBo midFrameAnno = videoAnnoInfoList.stream().filter(x -> x.getRectangle().getData().getPoint1().getZ() > groupInfo.getStartFrame().intValue() && x.getRectangle().getData().getPoint1().getZ() < groupInfo.getEndFrame().intValue()).findFirst().orElse(null);

            if (null == startFrameAnno || null == endFrameAnno || null == midFrameAnno) {
                return RespResult.secByError(RespCodeEnum.ERROR.getCode(), "帧段：" + groupInfo.getStartFrame() + " - " + groupInfo.getEndFrame() + " 标注框不符合要求");
            }

            checkResult.setStartFrameAnno(startFrameAnno);
            checkResult.setMidFrameAnno(midFrameAnno);
            checkResult.setEndFrameAnno(endFrameAnno);


            if (!ArrayUtil.isEmpty(algAlrList)) {
                checkResult.setPreAlgTime(algAlrList.get(0).getCreateTime());
            }
            checkResult.setAlgAlrList(algAlrList);
        }

        return RespResult.ok(checkResultList);
    }

    private RespResult<AnnoLesionToolEntity> checkLesionAndType4VideoTrace(Integer taskId) {
        AnnoLesionToolEntity cond = new AnnoLesionToolEntity();
        cond.setTaskId(taskId);
        cond.setBizType(LesionToolBizTypeEnum.ALG_TRACE.getBizType());
        cond.setTool(ToolEnum.RECTANGLE.getValue());
        List<AnnoLesionToolEntity> altList = altDao.findByCondition(cond, null);
        if (ArrayUtil.isEmpty(altList)) {
            return RespResult.secByError(RespCodeEnum.ERROR.getCode(), "未设置追踪病灶以及对应的矩形工具");
        }
        return RespResult.ok(altList.get(0));
    }

    private VideoTraceInDto packVideoTraceReqDto(List<VedioTraceCheckResultBo> checkResultList, String videoFilePath) {

        // 筛选：
        // 1、有算法结果的，判断起始、结束、关键帧是否有修改，有修改的需要重新追踪，否则不追踪
        // 2、无算法结果的需要追踪
        List<NoduleItemDto> itemDtos = checkResultList.stream().filter(x -> {
            if (ArrayUtil.isEmpty(x.getAlgAlrList())) {
                return true;
            }
            if (x.getStartFrameAnno().getLastUpdateTime().after(x.getPreAlgTime())) {
                return true;
            }
            if (x.getMidFrameAnno().getLastUpdateTime().after(x.getPreAlgTime())) {
                return true;
            }
            if (x.getEndFrameAnno().getLastUpdateTime().after(x.getPreAlgTime())) {
                return true;
            }
            return false;
        }).map(item -> {
            RectangleStruct startRectangle = item.getStartFrameAnno().getRectangle();
            RectangleStruct midRectangle = item.getMidFrameAnno().getRectangle();
            RectangleStruct endRectangle = item.getEndFrameAnno().getRectangle();

            List<Double> boundingBoxByStart = RectangleUtil.getLeftTopAndWightHeiht(startRectangle.getData().getPoint1().getX(), startRectangle.getData().getPoint1().getY(), startRectangle.getData().getPoint2().getX(), startRectangle.getData().getPoint2().getY());
            List<Double> boundingBoxByMid = RectangleUtil.getLeftTopAndWightHeiht(midRectangle.getData().getPoint1().getX(), midRectangle.getData().getPoint1().getY(), midRectangle.getData().getPoint2().getX(), midRectangle.getData().getPoint2().getY());
            List<Double> boundingBoxByEnd = RectangleUtil.getLeftTopAndWightHeiht(endRectangle.getData().getPoint1().getX(), endRectangle.getData().getPoint1().getY(), endRectangle.getData().getPoint2().getX(), endRectangle.getData().getPoint2().getY());

            return new NoduleItemDto(item.getGroupId(),
                    new InfoDto(startRectangle.getData().getPoint1().getZ(), boundingBoxByStart),
                    new InfoDto(midRectangle.getData().getPoint1().getZ(), boundingBoxByMid),
                    new InfoDto(endRectangle.getData().getPoint1().getZ(), boundingBoxByEnd));
        }).collect(Collectors.toList());

        String resJsonSaveFolder = Paths.get(annoNacosProperties.getUltrasoundAlgSharePath(), "video-trace", DateUtil.getDateTimeRandomFilePath()).toString();
        return new VideoTraceInDto(videoFilePath, resJsonSaveFolder, itemDtos);
    }

}
